/**
 * 
 */
package ac.svn.accesscontrol.controller;

import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import ac.notification.event.INotificationListener;
import ac.notification.event.NotificationEvent;
import ac.notification.manager.NotificationRegistrar;
import ac.svn.accesscontrol.context.Context;
import ac.svn.accesscontrol.context.IContextConstants;
import ac.svn.accesscontrol.manager.SVNInfoManager;
import ac.svn.accesscontrol.manager.SVNInfoManagerHelper;
import ac.svn.accesscontrol.store.AccessUnit;
import ac.svn.accesscontrol.store.IConstants;
import ac.svn.accesscontrol.store.IResources;
import ac.svn.accesscontrol.store.ISVNIdentifier;
import ac.svn.accesscontrol.store.ISVNItem;
import ac.svn.accesscontrol.store.SVNLocation;
import ac.svn.accesscontrol.ui.IListItemTextFormatter;
import ac.svn.accesscontrol.ui.helper.formatter.AccessUnitOwnerNameFormatter;
import ac.svn.accesscontrol.ui.model.ThreadedListModel;
import ac.svn.accesscontrol.ui.renderer.SimpleIconRenderer;
import ac.svn.accesscontrol.ui.window.AccessRightsAssignmentWindow;
import ac.svn.accesscontrol.utils.UIUtility;
import ac.svn.accesscontrol.utils.Utility;

/**
 * @author Ankush Chaudhary
 *
 */
public class AccessRightEditingController implements INotificationListener,
ISVNIdentifier,
IContextConstants
{
	private AccessRightsAssignmentWindow assignmentWindow;
	private Context editingContext = null;
	private ThreadedListModel availableListModel = null;
	private ThreadedListModel assigndListModel = null;
	private Map<String,AccessUnit> accessUnitsMap = null;
	public AccessRightEditingController()
	{
		assignmentWindow = new AccessRightsAssignmentWindow();
		availableListModel = new ThreadedListModel();
		assigndListModel = new ThreadedListModel();

		SimpleIconRenderer iconRenderer = new SimpleIconRenderer(IResources.LOC_ICN_USER_SMALL);
		IListItemTextFormatter formatter = new AccessUnitOwnerNameFormatter();
		iconRenderer.setTextFormatter(formatter);

		assignmentWindow.getAvailableItemsView().setCellRenderer(iconRenderer);
		assignmentWindow.getAvailableItemsView().setModel(availableListModel);

		assignmentWindow.getAssignedItemsView().setCellRenderer(iconRenderer);
		assignmentWindow.getAssignedItemsView().setModel(assigndListModel);

		assignmentWindow.getInformationLabel().setIcon(Utility.getImageIcon(IResources.LOC_ICN_EDIT_LOCATION_L));

		//Add Escape Key Listener
		Utility.addEscapeListener(assignmentWindow);

		registerListeners();
		NotificationRegistrar.registerListener(this);
	}

	/***
	 * Method is used to add listeners on various UI components.
	 */
	private void registerListeners() 
	{

		UIUtility.addPopupListener(assignmentWindow.getAvailableItemsView(), IConstants.VALUE_POPUP_TYPE_GROUP_USER);

		/**
		 * Below listener provides the functionality of moving items from list just by pressing -> button.
		 */
		assignmentWindow.getAvailableItemsView().addKeyListener(new KeyAdapter() 
		{
			@Override
			public void keyReleased(KeyEvent arg0) 
			{
				int kc = arg0.getKeyCode();
				if((KeyEvent.VK_RIGHT == kc) ||
						(KeyEvent.VK_ENTER == kc))
				{
					addSelectedItemsToAssigned();
				}
			}			
		});

		/**
		 * Below listener provides the functionality of moving items from list just by pressing <- button.
		 */
		assignmentWindow.getAssignedItemsView().addKeyListener(new KeyAdapter() 
		{
			@Override
			public void keyReleased(KeyEvent arg0) 
			{
				int kc = arg0.getKeyCode();
				if((KeyEvent.VK_LEFT == kc) ||
						(KeyEvent.VK_DELETE == kc))
				{
					removeSelectedItemsFromAssigned();
				}
			}			

		});

		/***
		 * Listener provides the functionality of added selected items from Available list to Assigned list.
		 */
		assignmentWindow.getAddButton().addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) 
			{
				addSelectedItemsToAssigned();
			}
		});		

		/***
		 * Listener provides the functionality of removing items from Assigned list.  
		 */
		assignmentWindow.getRemoveButton().addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) 
			{
				removeSelectedItemsFromAssigned();
			}
		});

		/**
		 * Listener provides handling on Ok button press.
		 */
		assignmentWindow.getOkButton().addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent ae) {
				finalizeOperation();
			}
		});

		/**
		 * Listener perform check of multiple selections on the Assigned list, as only two checkboxes are there for Read/Write
		 * changes, Read and Write rights for multiple items cannot be modified simultaneously.
		 * To make sure RW rights are changed only for single item, RW check boxes are disabled when multiple selections are
		 * made on Assigned list. 
		 */
		assignmentWindow.getAssignedItemsView().addListSelectionListener(new ListSelectionListener() {
			@Override
			public void valueChanged(ListSelectionEvent lse) 
			{
				//Do only when items are selected in the list.
				if(false == lse.getValueIsAdjusting())
				{
					//Get list of selected items on list.
					Object[] selectedItems = assignmentWindow.getAssignedItemsView().getSelectedValues();
					//If at all selection is made on list.
					if((null != selectedItems) && (0 < selectedItems.length))
					{
						int selectedItemCount = selectedItems.length;
						//If multiple selections are made on the list.
						if(1 < selectedItemCount)
						{
							//Access rights for multiple users cannot be changed at once, so disable check boxes.
							enableRightModification(false);
						}
						else
						{
							//Enable Check boxes for access rights modification for a single selection.
							enableRightModification(true);

							//Display current RW status. We are sure that atleast one item is there in list.
							ISVNItem item = (ISVNItem) selectedItems[0];
							showAccessRights(item.getId());

						}
					}
				}
			}
		});

		/**
		 * Listener does the assignments of the rights to selected item in assigned list.
		 */
		ActionListener checkBoxListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				changeRights(assignmentWindow.getReadCheckBox().isSelected(),assignmentWindow.getWriteCheckBox().isSelected());				
			}
		};
		assignmentWindow.getReadCheckBox().addActionListener(checkBoxListener);
		assignmentWindow.getWriteCheckBox().addActionListener(checkBoxListener);



	}


	/***
	 * Method for sending notification subscriptions.
	 */
	@Override
	public Set<String> declaredSubscriptionSet() {
		Set<String> subscriptionSet = new HashSet<String>();
		subscriptionSet.add(CMD_MODIFY_ACCESS_RIGHTS);
		subscriptionSet.add(N_UPDATE_LISTINGS);
		return subscriptionSet;
	}

	@Override
	public void receiveEvent(NotificationEvent ne)
	{
		if(ne.getPropertyIdentifier().contains(CMD_MODIFY_ACCESS_RIGHTS))
		{
			editingContext = (Context) ne.getPropertyValue();
			EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() 
				{
					initializeOperation();
				}
			});
		}
		else	 if( ne.getPropertyIdentifier().contains(N_UPDATE_LISTINGS))
		{
			EventQueue.invokeLater(new Runnable() {
				@Override
				public void run() 
				{
					loadAvailableList();
				}
			});
		}
		
	}


	/***
	 * Method adds selected items on Available list to assigned items list.
	 */
	private void addSelectedItemsToAssigned() 
	{
		Object[] selectedItems = assignmentWindow.getAvailableItemsView().getSelectedValues();
		if(null != selectedItems)
		{
			for(Object obj:selectedItems)
			{
				ISVNItem item = (ISVNItem) obj;

				if(true == assigndListModel.addElementOnce(item))
				{
					if(false == accessUnitsMap.keySet().contains(item.getId()))
					{
						addAccessUnit(item.getId());
					}
				}

			}
			//Once additions are done refresh UI.
			Utility.asynchronousUpdate(assignmentWindow.getAssignedItemsView());
		}
	}

	/***
	 * Method removes selected items on assigned list.
	 */
	private void removeSelectedItemsFromAssigned() 
	{
		Object[] selectedItems = assignmentWindow.getAssignedItemsView().getSelectedValues();
		if(null != selectedItems)
		{
			for(Object obj:selectedItems)
			{
				ISVNItem item = (ISVNItem) obj;
				assigndListModel.removeItem(item);  
			}

			//Once additions are done refresh UI.
			Utility.asynchronousUpdate(assignmentWindow.getAssignedItemsView());
			assignmentWindow.getAssignedItemsView().clearSelection();
		}
	}

	/***
	 *  Method reset state of dialog and various UI components to make them ready for new operation.
	 */
	private void initializeOperation()
	{
		if(null != editingContext)
		{

			//Show header highlighting details of current operation.
			assignmentWindow.getInformationLabel().setText(createHeader());

			//Clear the map created for temporary access rights assignments.
			if(null != accessUnitsMap)
			{
				accessUnitsMap.clear();
				accessUnitsMap = null;
			}

			//Create a new temporary map for assignments in current scenario.
			accessUnitsMap = new HashMap<String, AccessUnit>();

			//List all Group and users.
			assigndListModel.clear();

			//If context is for editing, list assignment already there for user.
			int contextType = getContextOperation();

			loadAvailableList();

			if(VALUE_OP_TYPE_EDITING == contextType)
			{
				SVNLocation location = (SVNLocation) getContextValue(KEY_OPERATION_PARAM);
				if(null != location)
				{
					//Populate access units of current location in temporary map!
					populateAccessUnitList(location);

					Set<ISVNItem> users = new HashSet<ISVNItem>(0);
					for(String accessUnitId : location.getAccessUnits())
					{
						//Get user/group associated with this access unit.
						ISVNItem item = SVNInfoManagerHelper.getOwnerOfAccessUnit(accessUnitId, null);
						//If one found add it to displayable set.
						if(null != item)
						{
							users.add(item);
						}
					}
					assigndListModel.addElement(users);  
				}		
			}	    

			//Repaint UI to reflect recent changes in the List Models.
			Utility.asynchronousUpdate(assignmentWindow);

			//Un-select  any selected items from previous operation.
			assignmentWindow.getAssignedItemsView().clearSelection();


			//Display window after all UI changes have been updated.
			assignmentWindow.setVisible(true);
		}
		else
		{
			Utility.showSystemNotification("<HTML>Context is not initialized/ready!<br>Go to Help!");
		}
	}

	private void loadAvailableList()
	{
		availableListModel.clear();
		//List all items(Groups and Users) from SVN into Available List.
		availableListModel.addElement(SVNInfoManager.getInfoManager().getAllGroups());
		availableListModel.addElement(SVNInfoManager.getInfoManager().getAllUsers());			
		assignmentWindow.getAvailableItemsView().clearSelection();
	}

	private void finalizeOperation()
	{
		//Check if context is still available.
		if(null != editingContext)
		{
			//Clear selections from assigned list.
			assignmentWindow.getAssignedItemsView().clearSelection();

			//Get list of all items present on Assigned List.
			List<ISVNItem> selectedItems = ((ThreadedListModel)assignmentWindow.getAssignedItemsView().getModel()).getItems();

			//Extract location from context.
			ISVNItem isvnItem = (ISVNItem) getContextValue(KEY_OPERATION_PARAM);

			//Check if at all present.
			if(null != isvnItem)
			{
				if(isvnItem instanceof SVNLocation)
				{
					SVNLocation svnLocation = (SVNLocation) isvnItem;

					//If items are present on Assigned items list.
					if((null != selectedItems) && (false == selectedItems.isEmpty()))
					{
						//Selected items are the only items which are allowed access. Items present earlier and are longer part of current assignment should be  removed.
						//Get list of old assignments
						Set<String> oldItems = svnLocation.getAccessUnits();

						//Final assignment list.
						Set<String> retainedItems = new HashSet<String>();

						//Get accessUnit id for assignments.
						for(ISVNItem item : selectedItems)
						{
							String ownerId = item.getId();
							retainedItems.add(accessUnitsMap.get(ownerId).getAccessUnitId());

							//Persist access unit so it can be access elsewhere.
							SVNInfoManager.getInfoManager().updateAccessUnit(accessUnitsMap.get(ownerId));
						}						

						//Set new assignments to Location.
						svnLocation.setAccessUnits(retainedItems);

						//Remove old accessUnits present in SVN Information Manager which are no longer required.
						for(String accessUnitId: oldItems)
						{
							//If old SVN unit is not part of current assignment, remove it from repository.
							if(false == retainedItems.contains(accessUnitId))
							{
								SVNInfoManager.getInfoManager().removeAccessUnit(accessUnitId);
							}
						}


						//SVNInfoManager.getInfoManager().updateLocation(svnLocation);
					}
					else
					{
						//Get list of Access unit created for this location.
						List<String> existingAssignments = new ArrayList<String>(svnLocation.getAccessUnits());

						//Remove these access units from SVNInformationManager
						for(String accessUnitId: existingAssignments)
						{
							SVNInfoManager.getInfoManager().removeAccessUnit(accessUnitId);
						}

						//Remove mapping from Location.
						svnLocation.getAccessUnits().clear();
					}				
				}
				else
				{
					System.err.println("AccessRightEditingController.finalizeOperation(): No location in context!");
				}
			}
		}
		else
		{
			Utility.showSystemNotification("<HTML>Context is not initialized/ready!<br>Go to Help!");
		}

		assignmentWindow.setVisible(false);
	}



	private int getContextOperation()
	{
		int contextType = IContextConstants.VALUE_OP_TYPE_UNDEFINED;
		if(null != editingContext)
		{
			contextType = ((Integer) editingContext.get(KEY_OPERATION_TYPE)).intValue();
		}
		else
		{
			Utility.showSystemNotification("<HTML>Context is not initialized/ready!<br>Go to Help!");
		}
		return contextType;	    
	}

	private Object getContextValue(String key)
	{
		Object value = null;
		if(null != editingContext)
		{
			value =  editingContext.get(key);
		}
		else
		{
			Utility.showSystemNotification("<HTML>Context is not initialized/ready!<br>Go to Help!");
		} 
		return value;	    
	}

	/***
	 * Method Enables and disables access rights check boxes.
	 * @param b
	 */
	private void enableRightModification(boolean b)
	{
		assignmentWindow.getReadCheckBox().setEnabled(b);
		assignmentWindow.getWriteCheckBox().setEnabled(b);
	}

	/***
	 * 
	 */
	private void showAccessRights(String itemId)
	{
		if(null != getCurrentLocation())
		{
			//Get access item corresponding to selected item.
			AccessUnit accessUnit = accessUnitsMap.get(itemId);

			//If it already present on temporary access rights map, use it otherwise create new with only read enabled.
			if(null == accessUnit)
			{
				addAccessUnit(itemId);
			}

			//Show current RW state for current selected item.
			assignmentWindow.getReadCheckBox().setSelected(accessUnit.isReadEnabled());
			assignmentWindow.getWriteCheckBox().setSelected(accessUnit.isWriteEnabled());
		}
	}


	/***
	 * Method adds a new access unit if not already present on temporary assignment map.
	 */

	private void addAccessUnit(String ownerId)
	{
		AccessUnit accessUnit = new AccessUnit(ownerId);
		accessUnit.setLocationPath(getCurrentLocation());
		accessUnit.setReadEnabled(true);
		accessUnit.setWriteEnabled(false);
		accessUnitsMap.put(ownerId, accessUnit);
	}

	/***
	 *	Method returns ID of location, getting created/updated in current context. 
	 * @return
	 */
	private String getCurrentLocation()
	{
		//Variable to hold the identifier of location.
		String location = null;

		//Extract location from context.
		ISVNItem isvnItem = (ISVNItem) getContextValue(KEY_OPERATION_PARAM);

		//Check if at all present.
		if(null != isvnItem)
		{
			if(isvnItem instanceof SVNLocation)
			{
				SVNLocation svnLocation = (SVNLocation) isvnItem;
				location = svnLocation.getId();
			}
		}
		else
		{
			String message = "Unable to proceed as location information is not available, try edit/create location again!";
			//Display message stating location is not present in context for access modifications.
			JOptionPane.showMessageDialog(assignmentWindow,message,"No Location specified!",JOptionPane.ERROR_MESSAGE);
		}

		return location;
	}


	/***
	 * Method populates temporary RW assignment Map with access rights which are already there for current location.
	 * @param svnLocation
	 */
	private void populateAccessUnitList(SVNLocation svnLocation)
	{
		//If location is present.
		if(null != svnLocation)
		{

			//Get list of identifier corresponding to access unit assigned to current location.
			Set<String> accessUnits = svnLocation.getAccessUnits();

			//Get reference to these access units and save in temporary map next to access unit owner.
			for(String accessUnitId : accessUnits)
			{
				AccessUnit accessUnit = SVNInfoManager.getInfoManager().getAccessUnit(accessUnitId);

				//Check access unit is present are not, if present then use otherwise non existent access unit is removed on finalization of operation.
				if(null != accessUnit)
				{
					accessUnitsMap.put(accessUnit.getOwnerId(), accessUnit);
				}
			}
		}
	}

	/***
	 * Method changes the read/write rights for selected item on assigned list.
	 * No changes are made if there is no selection on list.
	 * @param r
	 * @param w
	 */
	private void changeRights(boolean r, boolean w)
	{
		//Get selected item on assigned item list.
		Object selectedItem = assignmentWindow.getAssignedItemsView().getSelectedValue();

		//Check if selection is present at all.
		if(null != selectedItem)
		{
			//Convert object to ISVNItem so that id can be obtained.
			ISVNItem item = (ISVNItem) selectedItem; 

			//Get temporary access unit corresponding to currently selected item.
			AccessUnit accessUnit = accessUnitsMap.get(item.getId());

			//If any access unit exists.
			if(null != accessUnit)
			{
				//Reflect changes made on UI into AccessUnit.				
				accessUnit.setReadEnabled(r);
				accessUnit.setWriteEnabled(w);
			}
		}
	}

	/***
	 *  Method creates a display header for current operation in progress. 
	 */
	private String createHeader()
	{
		String headerString = "<HTML><p align=\"CENTER\">Welcome to location access rights editor, this dialog will help you in assigning and removing access rights for a location!</p></HTML>";
		if(null != editingContext)
		{
			SVNLocation location = (SVNLocation) editingContext.get(KEY_OPERATION_PARAM);
			String opType =(editingContext.get(KEY_OPERATION_TYPE).toString().equals("2") ? "Edit":"Add");
			headerString = String.format("<HTML> <b>Location Details:</b><br>" +
					"Name: %s<br>" +
					"Path: %s<br>" +
					"Rights:[%d]<br>" +
					"Current Operation: %s" +
					"</HTML>",
					location.getLocationName(),
					location.getLocationPath(),
					location.getAccessUnits().size(),
					opType); 
		}		
		return headerString;
	}
}
